/*
 ------------------------------------------------------------------------------
 Copyright (C) 2009-2011 Eternal Games.

 This file is part of the Eternal Tech source code.

 The Eternal Tech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Eternal Tech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Eternal Tech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// r_cull.cpp - Frustum culling functions
//


#include "r_local.h"


/*
 ==============================================================================

    LIGHT FRUSTUM CULLING

 ==============================================================================
*/


/*
 ==================
 R_LightCullBox
 ==================
*/
int R_LightCullBox (lightSource_t *light, vec3_t mins, vec3_t maxs, int clipFlags)
{
	cplane_t   *plane;
	int		   side, cullBits;
	int		   i;

	cullBits = CULL_IN;

	// Check against frustum planes
	for (i = 0, plane = light->frustum; i < NUM_LIGHT_FRUSTUM_PLANES; i++, plane++)
	{
		if (!(clipFlags & BIT(i)))
			continue;

		side = BoxOnPlaneSide(mins, maxs, plane);

		if (side == PLANESIDE_BACK)
		{
			rg.pc.lightBoxOut++;
			return CULL_OUT;
		}
	
		if (side == PLANESIDE_CROSS)
			cullBits |= BIT(i);
	}

	if (cullBits != CULL_IN)
		rg.pc.lightBoxClip++;
	else
		rg.pc.lightBoxIn++;

	return cullBits;
}

/*
 ==================
 R_LightCullSphere
 ==================
*/
int R_CullLightSphere (lightSource_t *light, const vec3_t center, float radius, int clipFlags)
{
	cplane_t   *plane;
	int		   side, cullBits;
	int		   i;

	cullBits = CULL_IN;

	// Check against frustum planes
	for (i = 0, plane = light->frustum; i < NUM_LIGHT_FRUSTUM_PLANES; i++, plane++)
	{
		if (!(clipFlags & BIT(i)))
			continue;

		side = SphereOnPlaneSide(center, radius, plane);

		if (side == PLANESIDE_BACK)
		//{
			rg.pc.lightSphereOut++;
			return CULL_OUT;
		//}

		if (side == PLANESIDE_CROSS)
			cullBits |= BIT(i);
	}

	if (cullBits != CULL_IN)
		rg.pc.lightSphereClip++;
	else
		rg.pc.lightSphereIn++;

	return cullBits;
}

/*
 ==================
 R_LightCullEntityBox
 ==================
*/
int R_LightCullEntityBox (lightSource_t *light, renderEntity_t *entity, const vec3_t mins, const vec3_t maxs, int clipFlags)
{
	cplane_t   *plane;
	vec3_t	   corner, corners[8];
	int		   side, cullBits;
	int		   i, j;

	cullBits = CULL_IN;

	// Compute the corners of the bounding volume
	for (i = 0; i < 8; i++)
	{
		corner[0] = (i & 1) ? mins[0] : maxs[0];
		corner[1] = (i & 2) ? mins[1] : maxs[1];
		corner[2] = (i & 4) ? mins[2] : maxs[2];

		// Rotate and translate into world space
		VectorRotate(corner, entity->axis, corners[i]);
		corners[i][1] = -corners[i][1];
		VectorAdd(corners[i], entity->origin, corners[i]);
	}

	// Check against frustum planes
	for (i = 0, plane = light->frustum; i < NUM_LIGHT_FRUSTUM_PLANES; i++, plane++)
	{
		if (!(clipFlags & BIT(i)))
			continue;

		side = 0;

		for (j = 0; j < 8; j++)
		{
			side |= PointOnPlaneSide(corners[j], 0.0f, plane);

			if (side == PLANESIDE_CROSS)
				break;
		}

		if (side == PLANESIDE_BACK)
		{
			rg.pc.lightEntBoxOut++;
			return CULL_OUT;
		}

		if (side == PLANESIDE_CROSS)
			cullBits |= BIT(i);
	}

	if (cullBits != CULL_IN)
		rg.pc.lightEntBoxClip++;
	else
		rg.pc.lightEntBoxIn++;

	return cullBits;
}

/*
 ==================
 R_LightCullEntitySphere
 ==================
*/
int R_LightCullEntitySphere (lightSource_t *light, renderEntity_t *entity, float radius, int clipFlags)
{
	cplane_t   *plane;
	vec3_t	   center, point;
	int		   side, cullBits;
	int		   i;

	cullBits = CULL_IN;

	// Rotate and translate into world space
	VectorRotate(center, entity->axis, point);
	point[1] = -point[1];
	VectorAdd(point, entity->origin, point);

	// Check against frustum planes
	for (i = 0, plane = light->frustum; i < NUM_LIGHT_FRUSTUM_PLANES; i++, plane++)
	{
		if (!(clipFlags & BIT(i)))
			continue;

		side = SphereOnPlaneSide(point, radius, plane);

		if (side == PLANESIDE_BACK)
		{
			rg.pc.lightEntSphereOut++;
			return CULL_OUT;
		}

		if (side == PLANESIDE_CROSS)
			cullBits |= BIT(i);
	}

	if (cullBits != CULL_IN)
		rg.pc.lightEntSphereClip++;
	else
		rg.pc.lightEntSphereIn++;

	return cullBits;
}

/*
 ==================
 R_CullLightBox
 ==================
*/
int R_CullLightBox (const vec3_t mins, const vec3_t maxs, int clipFlags)
{
	cplane_t   *plane;
	int		   side, cullBits;
	int		   i;

	cullBits = CULL_IN;

	// Check against frustum planes
	for (i = 0, plane = rg.renderViewParms.frustum; i < NUM_FRUSTUM_PLANES; i++, plane++)
	{
		if (!(clipFlags & BIT(i)))
			continue;

		side = BoxOnPlaneSide(mins, maxs, plane);

		if (side == PLANESIDE_BACK)
		{
			rg.pc.lightBoxOut++;
			return CULL_OUT;
		}

		if (side == PLANESIDE_CROSS)
			cullBits |= BIT(i);
	}

	if (cullBits != CULL_IN)
		rg.pc.lightBoxClip++;
	else
		rg.pc.lightBoxIn++;

	return cullBits;
}

/*
 ==================
 R_CullLightVolumeBox
 ==================
*/
int R_CullLightVolumeBox (const vec3_t corners[8], int planeBits)
{
	cplane_t   *plane;
	int		   side, cullBits;
	int		   i, j;

	cullBits = CULL_IN;

	// Check against frustum planes
	for (i = 0, plane = rg.renderViewParms.frustum; i < NUM_FRUSTUM_PLANES; i++, plane++)
	{
		if (!(planeBits & BIT(i)))
			continue;

		side = 0;

		for (j = 0; j < 8; j++)
		{
			side |= PointOnPlaneSide(corners[j], 0.0f, plane);

			if (side == PLANESIDE_CROSS)
				break;
		}

		if (side == PLANESIDE_BACK)
		{
			rg.pc.lightBoxOut++;
			return CULL_OUT;
		}

		if (side == PLANESIDE_CROSS)
			cullBits |= BIT(i);
	}

	if (cullBits != CULL_IN)
		rg.pc.lightBoxClip++;
	else
		rg.pc.lightBoxIn++;

	return cullBits;
}


/*
 ==============================================================================

    ENTITY FRUSTUM CULLING

 ==============================================================================
*/


/*
 ==================
 R_CullEntityBox

 - Works but does not cull through walls
 ==================
*/
int R_CullEntityBox (renderEntity_t *entity, const vec3_t mins, const vec3_t maxs, int clipFlags)
{
	cplane_t   *plane;
	vec3_t	   corner, worldCorners[8];
	int		   side, cullBits;
	int		   i, j;

	cullBits = CULL_IN;

	// Compute the corners of the bounding volume
	for (i = 0; i < 8; i++)
	{
		corner[0] = (i & 1) ? mins[0] : maxs[0];
		corner[1] = (i & 2) ? mins[1] : maxs[1];
		corner[2] = (i & 4) ? mins[2] : maxs[2];

		// Rotate and translate into world space
		VectorRotate(corner, entity->axis, worldCorners[i]);
		worldCorners[i][1] = -worldCorners[i][1];
		VectorAdd(worldCorners[i], entity->origin, worldCorners[i]);
	}

	// Check against frustum planes
	for (i = 0, plane = rg.renderViewParms.frustum; i < NUM_FRUSTUM_PLANES; i++, plane++)
	{
		if (!(clipFlags & BIT(i)))
			continue;

		side = 0;

		for (j = 0; j < 8; j++)
		{
			side |= PointOnPlaneSide(worldCorners[j], 0.0f, plane);

			if (side == PLANESIDE_CROSS)
				break;
		}

		if (side == PLANESIDE_BACK)
		{
			rg.pc.entityBoxOut++;
			return CULL_OUT;
		}

		if (side == PLANESIDE_CROSS)
			cullBits |= BIT(i);
	}

	if (cullBits != CULL_IN)
		rg.pc.entityBoxClip++;
	else
		rg.pc.entityBoxIn++;

	return cullBits;
}

/*
 ==================
 R_CullEntitySphere

 - Works but does not cull through walls
 ==================
*/
int R_CullEntitySphere (renderEntity_t *entity, const vec3_t center, float radius, int clipFlags)
{
	cplane_t   *plane;
	//vec3_t	   worldCenter;
	int		   side, cullBits;
	int		   i;

	cullBits = CULL_IN;

	// Rotate and translate into world space
	//VectorRotate(center, entity->axis, worldCenter);
	//worldCenter[1] = -worldCenter[1];
	//VectorAdd(worldCenter, entity->origin, worldCenter);

	// Check against frustum planes
	for (i = 0, plane = rg.renderViewParms.frustum; i < NUM_FRUSTUM_PLANES; i++, plane++)
	{
		if (!(clipFlags & BIT(i)))
			continue;

		side = SphereOnPlaneSide(center, radius, plane);

		if (side == PLANESIDE_BACK)
		{
			rg.pc.entitySphereOut++;
			return CULL_OUT;
		}

		if (side == PLANESIDE_CROSS)
			cullBits |= BIT(i);
	}

	if (cullBits != CULL_IN)
		rg.pc.entitySphereClip++;
	else
		rg.pc.entitySphereIn++;

	return cullBits;
}


/*
 ==============================================================================

    SURFACE FRUSTUM CULLING

 ==============================================================================
*/


/*
 ==================
 R_CullLightSurface
 ==================
*/
bool R_CullLightSurface (surface_t *surface, renderEntity_t *entity, lightSource_t *lightSource, const vec3_t viewOrigin, int clipFlags)
{
	material_t   *material = surface->texInfo->material;
	int			 side;

	// Cull face
	if (surface->plane && material->deform == DFRM_NONE)
	{
		side = PointOnPlaneSide(viewOrigin, 0.0f, surface->plane);

		if (material->cullType == CT_FRONT_SIDED)
		{
			if (!(surface->flags & SURF_PLANEBACK))
			{
				if (side != PLANESIDE_FRONT)
					return false;
			}
			else
			{
				if (side != PLANESIDE_BACK)
					return false;
			}
		}
		else if (material->cullType == CT_BACK_SIDED)
		{
			if (!(surface->flags & SURF_PLANEBACK))
			{
				if (side != PLANESIDE_BACK)
					return false;
			}
			else
			{
				if (side != PLANESIDE_FRONT)
					return false;
			}
		}
	}

	// Cull bounding box
	if (clipFlags != CULL_IN)
	{
		if (entity == rg.worldEntity)
		{
			if (R_LightCullBox(lightSource, surface->mins, surface->maxs, clipFlags) == CULL_OUT)
				return false;
		}
		else
		{
			if (R_LightCullEntityBox(lightSource, entity, surface->mins, surface->maxs, clipFlags) == CULL_OUT)
				return false;
		}
	}

	return true;
}

/*
 ==================
 R_CullSurface
 ==================
*/
bool R_CullSurface (surface_t *surface, renderEntity_t *entity, const vec3_t viewOrigin, int clipFlags)
{
	material_t   *material = surface->texInfo->material;
	int			 side;

	// Cull face
	if (surface->plane && material->deform == DFRM_NONE)
	{
		side = PointOnPlaneSide(viewOrigin, 0.0f, surface->plane);

		if (material->cullType == CT_FRONT_SIDED)
		{
			if (!(surface->flags & SURF_PLANEBACK))
			{
				if (side != PLANESIDE_FRONT)
					return false;
			}
			else
			{
				if (side != PLANESIDE_BACK)
					return false;
			}
		}
		else if (material->cullType == CT_BACK_SIDED)
		{
			if (!(surface->flags & SURF_PLANEBACK))
			{
				if (side != PLANESIDE_BACK)
					return false;
			}
			else
			{
				if (side != PLANESIDE_FRONT)
					return false;
			}
		}
	}

	// Cull bounding box
	if (clipFlags != CULL_IN)
	{
		if (entity == rg.worldEntity)
		{
			if (R_CullBox(surface->mins, surface->maxs, clipFlags) == CULL_OUT)
				return false;
		}
		else
		{
			if (R_CullEntityBox(entity, surface->mins, surface->maxs, clipFlags) == CULL_OUT)
				return false;
		}
	}

	return true;
}


/*
 ==============================================================================

    FRUSTUM CULLING

 ==============================================================================
*/


/*
 ==================
 R_CullBox
 ==================
*/
int R_CullBox (vec3_t mins, vec3_t maxs, int clipFlags)
{
	cplane_t   *plane;
	int			side, cullBits;
	int			i;

	cullBits = CULL_IN;

	// Check against frustum planes
	for (i = 0, plane = rg.renderViewParms.frustum; i < NUM_FRUSTUM_PLANES; i++, plane++)
	{
		if (!(clipFlags & BIT(i)))
			continue;

		side = BoxOnPlaneSide(mins, maxs, plane);

		if (side == PLANESIDE_BACK)
		{
			rg.pc.worldBoxOut++;
			return CULL_OUT;
		}

		if (side == PLANESIDE_CROSS)
			cullBits |= BIT(i);
	}

	if (cullBits != CULL_IN)
		rg.pc.worldBoxClip++;
	else
		rg.pc.worldBoxIn++;

	return cullBits;
}

/*
 ==================
 R_CullSphere
 ==================
*/
int R_CullSphere (const vec3_t center, float radius, int clipFlags)
{
	cplane_t   *plane;
	int		   side, cullBits;
	int		   i;

	cullBits = CULL_IN;

	// Check against frustum planes
	for (i = 0, plane = rg.renderViewParms.frustum; i < NUM_FRUSTUM_PLANES; i++, plane++)
	{
		if (!(clipFlags & BIT(i)))
			continue;

		side = SphereOnPlaneSide(center, radius, plane);

		if (side == PLANESIDE_BACK)
		{
			rg.pc.worldSphereOut++;
			return CULL_OUT;
		}

		if (side == PLANESIDE_CROSS)
			cullBits |= BIT(i);
	}

	if (cullBits != CULL_IN)
		rg.pc.worldSphereClip++;
	else
		rg.pc.worldSphereIn++;

	return cullBits;
}